package com.my.geojson.util.file;

import com.alibaba.fastjson.JSONObject;
import com.bedatadriven.jackson.datatype.jts.JtsModule;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.my.geojson.util.gps.GPSUtils;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.FeatureIterator;
import org.geotools.geojson.feature.FeatureJSON;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.json.simple.JSONArray;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory;
import org.locationtech.jts.io.WKTReader;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.referencing.operation.MathTransform;

import java.io.*;
import java.nio.charset.Charset;
import java.util.*;

/**
 *
 */
public class TransformShapeFileUtils {
    private static PrecisionModel precisionModel = new PrecisionModel();
    private static GeometryFactory geometryFactory = new GeometryFactory(precisionModel, 0, PackedCoordinateSequenceFactory.DOUBLE_FACTORY);
    private static WKTReader wktReader = new WKTReader(geometryFactory);
    private static ObjectMapper objectMapper = new ObjectMapper();

    static {
        objectMapper.registerModule(jtsModule());
    }

    public static JtsModule jtsModule() {
        return new JtsModule(geometryFactory);
    }

    private final static String shpPath = "C:/Users/Administrator/Desktop/四川省/四川省最新版省级地级市区县和乡镇级全套矢量范围数据/德阳市/中江县_行政边界/中江县_行政边界.shp";

    public static void main(String[] args) throws Exception {
        File file = new File(shpPath);
        transformShapeFileToJSONObject(file);
    }

    /**
     * 读取shapeFile文件通过序列化反序列化geoJson导出数据
     * @param file
     * @return
     * @throws IOException
     */
    public static GeoJsonRes transformShapeFileToJSONObject(File file) throws IOException {
        GeoJsonRes geoJsonRes = new GeoJsonRes();
        SimpleFeatureSource simpleFeatureSource = readStoreByShp(file);
        SimpleFeatureCollection featureCollection = simpleFeatureSource.getFeatures();
//        SimpleFeatureCollection featureCollection = readShp(file.getPath());
        com.alibaba.fastjson.JSONArray jsonArray84 = new com.alibaba.fastjson.JSONArray();
        com.alibaba.fastjson.JSONArray jsonArrayGcj02 = new com.alibaba.fastjson.JSONArray();
        com.alibaba.fastjson.JSONArray jsonArrayBD09 = new com.alibaba.fastjson.JSONArray();

        //边界街道边界
        FeatureIterator it = featureCollection.features();
        while (it.hasNext()) {
            SimpleFeature feature = (SimpleFeature) it.next();
            //机投桥街道
            String name = (String) feature.getProperty("Name").getValue();
            MultiPolygon geometry = (MultiPolygon) feature.getDefaultGeometry();
            System.out.println("转换前：" + name + "\n" + geometry.toString());
//                String geoJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(geometry);
//                System.out.println(geoJson);
            Geometry multiPolygon84 = crsTransform("EPSG:3395", "EPSG:4326", geometry);
            System.out.println("mactor转换84后：" + name + "\n" + multiPolygon84.toString());
            createJsonObjectFeatures(jsonArray84, multiPolygon84, feature);
//                String geoJson2 = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(geometry);
//                System.out.println(geoJson2);

            //84转GCJ02坐标系
            MultiPolygon multiPolygonGCJ02 = multiPolygonToGeoGSC(file, feature, multiPolygon84, "84-To-GCJ02");
            createJsonObjectFeatures(jsonArrayGcj02, multiPolygonGCJ02, feature);
            //GCJ02坐标系转BD09
            MultiPolygon multiPolygonBD09 = multiPolygonToGeoGSC(file, feature, multiPolygonGCJ02, "GCJ02-To-BD09");
            createJsonObjectFeatures(jsonArrayBD09, multiPolygonBD09, feature);

        }

        JSONObject featuresJsonObject84 = new JSONObject();
        featuresJsonObject84.put("type", "FeatureCollection");
        featuresJsonObject84.put("features", jsonArray84);
        geoJsonRes.setGeoJsonWgs84(featuresJsonObject84);

        JSONObject featuresJsonObjectGcj02 = new JSONObject();
        featuresJsonObjectGcj02.put("type", "FeatureCollection");
        featuresJsonObjectGcj02.put("features", jsonArrayGcj02);
        geoJsonRes.setGeoJsonGcj02(featuresJsonObjectGcj02);

        JSONObject featuresJsonObjectBD09 = new JSONObject();
        featuresJsonObjectBD09.put("type", "FeatureCollection");
        featuresJsonObjectBD09.put("features", jsonArrayBD09);
        geoJsonRes.setGeoJsonBD09(featuresJsonObjectBD09);
        return geoJsonRes;
    }

    private static MultiPolygon multiPolygonToGeoGSC(File file, SimpleFeature feature, Geometry multiPolygon, String geoGsc) {
        String name = (String) feature.getProperty("Name").getValue();
        int numGeometries = multiPolygon.getNumGeometries();
        //创建multi多边形集合
        List<Polygon> polygons = new ArrayList<>(numGeometries);

        for (int i = 0; i < numGeometries; i++) {
            Geometry geometryN = multiPolygon.getGeometryN(i);
            String geometryType = geometryN.getGeometryType();
            if ("Polygon".equals(geometryType)) {
                Polygon polygon = (Polygon) geometryN;

                //外部边界
                LinearRing exteriorRing = polygon.getExteriorRing();
                //转换调用
                Coordinate[] shellCoordinates = wgs84ToGcj02OrBD09(exteriorRing, geoGsc);
                //内部边界
                int numInteriorRing = polygon.getNumInteriorRing();
                List<Coordinate[]> holeCoordinateList = new ArrayList<>(numInteriorRing);
                for (int in = 0; in < numInteriorRing; in++) {
                    LinearRing interiorRingN = polygon.getInteriorRingN(in);
                    Coordinate[] holeCoordinates = wgs84ToGcj02OrBD09(interiorRingN, geoGsc);
                    holeCoordinateList.add(holeCoordinates);
                }
                Polygon newPolygon = getPolygon(shellCoordinates, holeCoordinateList);
                polygons.add(newPolygon);

            } else {
                throw new RuntimeException(file.getPath() + "几何图形为：" + geometryType + "不可处理");
            }
        }
        //组合multi多边形
        Polygon[] polygonArray = geometryFactory.toPolygonArray(polygons);
        MultiPolygon newMultiPolygon = geometryFactory.createMultiPolygon(polygonArray);
        System.out.println(geoGsc + "后：" + name + "\n" + newMultiPolygon.toString());

//                String geoJson3 = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(multiPolygon);
//                System.out.println(geoJson3);

        return newMultiPolygon;
    }

    private static void createJsonObjectFeatures(com.alibaba.fastjson.JSONArray jsonArray, Geometry multiPolygon, SimpleFeature feature) {
        JSONObject featureJsonObject = new JSONObject();
        featureJsonObject.put("id", feature.getID());
        featureJsonObject.put("type", "Feature");
        featureJsonObject.put("geometry", multiPolygon);
        Properties propertie = new Properties();
        propertie.put("layer", feature.getAttribute("Layer"));
        propertie.put("name", feature.getAttribute("Name"));
        featureJsonObject.put("properties", propertie);
        jsonArray.add(featureJsonObject);
    }


    /**
     * 84坐标转GCJ02或
     * CJ02转BD09
     *
     * @param exteriorRing
     * @return
     */
    private static Coordinate[] wgs84ToGcj02OrBD09(LinearRing exteriorRing, String geoGsc) {
        Coordinate[] coordinates = exteriorRing.getCoordinates();
        List<Coordinate> coordinateList = new ArrayList<>(coordinates.length);
        for (Coordinate coordinate : coordinates) {
            if ("84-To-GCJ02".equals(geoGsc)) {
                //84坐标转GCJ02
                double[] doublesGcj02 = GPSUtils.wgs84_To_Gcj02(coordinate.getY(), coordinate.getX());
                coordinateList.add(new Coordinate(doublesGcj02[1], doublesGcj02[0]));
            } else if ("GCJ02-To-BD09".equals(geoGsc)) {
                //GCJ02转BD09
                double[] doublesBD09 = GPSUtils.gcj02_To_Bd09(coordinate.getY(), coordinate.getX());
                coordinateList.add(new Coordinate(doublesBD09[1], doublesBD09[0]));
            }
        }
        //将list转换为 coordinate[]
        //转换后的数组
        Coordinate[] newCoordinates = CoordinateArrays.toCoordinateArray(coordinateList);
        return newCoordinates;
    }


    /**
     * 获面
     *
     * @param shellCoordinates   面边界坐标集合
     * @param holeCoordinateList 多个孔洞坐标集合
     */
    public static Polygon getPolygon(Coordinate[] shellCoordinates, List<Coordinate[]> holeCoordinateList) {
        LinearRing shell = geometryFactory.createLinearRing(shellCoordinates);
        LinearRing[] holes = new LinearRing[holeCoordinateList.size()];
        for (int m = 0; m < holeCoordinateList.size(); m++) {
            Coordinate[] holeCoordinates = holeCoordinateList.get(m);
            holes[m] = geometryFactory.createLinearRing(holeCoordinates);
        }
        return geometryFactory.createPolygon(shell, holes);
    }


    /**
     * Creates a Polygon with a hole from an exterior ring and an interior ring supplied by the
     * rings of Polygons.
     *
     * @param shell the exterior ring
     * @param hole  the interior ring
     * @return a Polygon with a hole
     */
    public Polygon polygon(Polygon shell, Polygon hole) {
        return geometryFactory.createPolygon(
                shell.getExteriorRing(), new LinearRing[]{hole.getExteriorRing()});
    }

    public static Geometry crsTransform(String srcCrs, String dstCrs, Geometry geometrySource) {
        try {
            MathTransform transform = CRS.findMathTransform(CRS.decode(srcCrs, true),
                    CRS.decode(dstCrs, true), false);
            Geometry dstGeometry = JTS.transform(geometrySource, transform);
            return dstGeometry;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
//        double[] source = new double[]{geometrySource.getCoordinate().getX(),geometrySource.getCoordinate().getY()};
    }

    /**
     * 解析shp文件
     *
     * @param filePath
     * @return
     * @throws Exception
     */
    public static Map ParsingShpFile(String filePath) throws Exception {
        File file = new File(filePath);
        if (!file.exists()) {
            throw new Exception("文件不存在!");
        }
        if (!filePath.endsWith("shp")) {
            throw new Exception("只能指定后缀为shp的文件");
        }
        Map map = new HashMap();
        List<Map> list = new ArrayList();
        //读取shp
        SimpleFeatureCollection colls1 = readShp(filePath);
        SimpleFeatureType schema = colls1.getSchema();
        Name name = schema.getGeometryDescriptor().getType().getName();
        ReferencedEnvelope bounds = colls1.getBounds();
        //拿到所有features
        SimpleFeatureIterator iters = colls1.features();
        String s = name.toString();
        if ("Point".equals(s)) {
            list = parsingPoint(iters);
        } else if ("MultiLineString".equals(s) || "MultiPolygon".equals(s)) {
            list = parsingLineOrPoly(iters);
        }
        map.put("data", list);
        map.put("maxX", bounds.getMaxX());
        map.put("minX", bounds.getMinX());
        map.put("maxY", bounds.getMaxY());
        map.put("minY", bounds.getMinY());
        map.put("shapeFile", name.toString());
        return map;
    }

    /**
     * 解析点数据
     *
     * @param iters
     * @return
     */
    public static List<Map> parsingPoint(SimpleFeatureIterator iters) {
        List<Map> list = new ArrayList();
        while (iters.hasNext()) {
            SimpleFeature sf = iters.next();
            Map map = new HashMap();
            Iterator<? extends Property> iterator = sf.getValue().iterator();
            while (iterator.hasNext()) {
                Property property = iterator.next();
                if (property.getValue() instanceof Point) {
                    map.put("PointX", ((Point) (property.getValue())).getX());
                    map.put("PointY", ((Point) (property.getValue())).getY());
                } else {
                    Name name = property.getName();//属性名称
                    Object value = property.getValue();//属性值
                    map.put(name, value);
                }
            }
            list.add(map);
        }
        iters.close();
        return list;
    }

    /**
     * 解析线和面
     *
     * @param iters
     * @return
     */
    public static List<Map> parsingLineOrPoly(SimpleFeatureIterator iters) {
        List<Map> list = new ArrayList();
        while (iters.hasNext()) {
            SimpleFeature sf = iters.next();
            Map map = new HashMap();
            Iterator<? extends Property> iterator = sf.getValue().iterator();
            while (iterator.hasNext()) {
                Property property = iterator.next();
                if (property.getValue() instanceof Geometry) {
                    Geometry geometry = (Geometry) property.getValue();
                    Coordinate[] coordinates = geometry.getCoordinates();
                    List<Map> paths = new ArrayList<Map>();
                    for (Coordinate coordinate : coordinates) {
                        Map path = new HashMap();
                        path.put("x", coordinate.x);
                        path.put("y", coordinate.y);
                        path.put("z", coordinate.z);
                        paths.add(path);
                    }
                    map.put("path", paths);
                } else {
                    Name name = property.getName();//属性名称
                    Object value = property.getValue();//属性值
                    map.put(name, value);
                }
            }
            list.add(map);
        }
        iters.close();
        return list;
    }

    public static SimpleFeatureCollection readShp(String path) {
        return readShp(path, null);

    }

    public static SimpleFeatureCollection readShp(String path, Filter filter) {
        SimpleFeatureSource featureSource = readStoreByShp(path);
        if (featureSource == null) {
            return null;
        }
        ;
        try {
            return filter != null ? featureSource.getFeatures(filter) : featureSource.getFeatures();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static SimpleFeatureSource readStoreByShp(String path) {
        File file = new File(path);
        FileDataStore store;
        SimpleFeatureSource featureSource = null;
        try {
            store = FileDataStoreFinder.getDataStore(file);
//            ((ShapefileDataStore) store).setCharset(Charset.forName("UTF-8"));
//            ((ShapefileDataStore) store).setCharset(Charset.forName("GBK"));
            ((ShapefileDataStore) store).setCharset(Charset.forName(getShapeFileCharsetName(path)));
            featureSource = store.getFeatureSource();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return featureSource;
    }

    public static SimpleFeatureSource readStoreByShp(File file) {
        FileDataStore store;
        SimpleFeatureSource featureSource = null;
        try {
            store = FileDataStoreFinder.getDataStore(file);
//            ((ShapefileDataStore) store).setCharset(Charset.forName("UTF-8"));
//            ((ShapefileDataStore) store).setCharset(Charset.forName("GBK"));
            ((ShapefileDataStore) store).setCharset(Charset.forName(getShapeFileCharsetName(file.getPath())));
            featureSource = store.getFeatureSource();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return featureSource;
    }


    /**
     * shp文件中对于中文编码有多种格式，即存在GBK又存在UTF-8
     * 由于ArcGIS版本的问题，在高版本中默认Shp文件的字符编码为UTF-8，而上述代码中指定编码格式为GBK，在代码中需要动态指定一下GBK和UTF-8，文件中的介绍如下： https://www.cnblogs.com/gisoracle/p/8098900.html
     * <p>
     * 我们采用的方法时读取shp文件同名的cpg文件，中存储了当前dbf文件的编码格式，可以通过打开cpg文件的内容，使用文件中的内容作为GeoTools的字符编码格式。
     *
     * @param path
     * @return
     * @throws Exception
     */
    private static String getShapeFileCharsetName(String path) throws Exception {
        File pFile = new File(path);
        if (pFile.exists() && !pFile.isFile()) {
            return "GBK";
        }
        File file = new File(path);
        String encode = "GBK";
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader(file));
            String tempString = null;
            // 一次读入一行，直到读入null为文件结束
            while ((tempString = reader.readLine()) != null) {
                // 显示行号
                if ("UTF-8".equals(tempString.toUpperCase())) {
                    encode = "UTF-8";
                    break;
                }
                break;
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e1) {
                }
            }
        }
        return encode;
    }


    /**
     * shp转换为Geojson
     *
     * @param shpPath  shp文件地址
     * @param jsonPath 要写入的json文件地址
     * @return
     */
    public static String shape2Geojson(String shpPath, String jsonPath) {
        FeatureJSON fjson = new FeatureJSON();
        StringBuffer sb = new StringBuffer();
        try {
            sb.append("{\"type\": \"FeatureCollection\",\"features\": ");

            //读取shp
            SimpleFeatureCollection colls = readShp(shpPath);
            //拿到所有features
            SimpleFeatureIterator itertor = colls.features();
            JSONArray array = new JSONArray();
            while (itertor.hasNext()) {
                SimpleFeature feature = itertor.next();
                StringWriter writer = new StringWriter();
                fjson.writeFeature(feature, writer);
                JSONObject json = JSONObject.parseObject(writer.toString());
                array.add(json);
            }
            itertor.close();
            sb.append(array.toString());
            sb.append("}");

            //写入文件
            FileOutputStream fos = new FileOutputStream(jsonPath, false);
            //true表示在文件末尾追加
            fos.write(sb.toString().getBytes());
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();

        }
        return sb.toString();
    }
}
